---
title: 'Best Practices'
description: 'Prompting Stagehand places an emphasis on being atomic and specific. Here are some guidelines to help you use Stagehand effectively.'
---

### Use Cursor rules for better AI suggestions

Most of the Stagehand team uses [Cursor](https://www.cursor.com/) to write code. Cursor has a feature called [rules](https://docs.cursor.com/context/rules-for-ai) that allows you to customize the AI's behavior. You can use these rules to make the AI more accurate when suggesting actions.

For Stagehand's Cursor rules, check out [this file](https://github.com/browserbase/stagehand-scaffold/blob/main/.cursorrules). 

If you're using Windsurf, you can use the same rules by adding them to your `.windsurfrules` file.

### Manage multiple pages explicitly (Stagehand v≥ 2.4.1, TypeScript)
<Tabs>
<Tab title="TypeScript">
Stagehand can now keep **multiple pages open at the same time** and run
`act()`, `observe()` and `extract()` on each of them.

#### What changed?

* **Before v2.4.1** Stagehand silently closed every new tab and re‑used the first tab.
  A single Page instance (`stagehand.page`) was always valid.
* **Since v2.4.1** New tabs stay open. You can switch between them or operate on all of them in parallel.

#### The live‑page proxy (`stagehand.page`)

For backward compatibility, `stagehand.page` is now a **live proxy** that always
points to the **most recently opened page**.
We recommend using it for things like agents, which often don't have the ability to operate on multiple pages.
If you need more control and precision, we recommend managing pages yourself.

#### Recommended workflow
<CodeGroup>
```typescript TypeScript
// Create (or let the site create) additional pages.
await stagehand.context.newPage(); // e.g. open a blank page
const pages = stagehand.context.pages();

const [page1, page2] = pages;

// Work on each page deterministically
await page1.goto('https://github.com/browserbase/stagehand');
await page2.goto('https://github.com/browserbase/stagehand-python');

const [{ extraction: repo1 }, { extraction: repo2 }] = await Promise.all([
    page1.extract('extract the repository name'),
    page2.extract('extract the repository name'),
]);

// should log "page1 repo: stagehand"
console.log(`page1 repo: ${repo1}`);

// should log "page2 repo: stagehand-python"
console.log(`page2 repo: ${repo2}`);
```
</CodeGroup>
</Tab>
<Tab title="Python">
<Tip>
Multi tab support is not yet implemented in Python, but it's coming soon!
Setting `use_api=True` in your stagehand config will keep tabs open.
</Tip>
</Tab>
</Tabs>

### Avoid sending sensitive information to LLMs

You can use `variables` in an `act` call to avoid sending sensitive information to LLMs.

<CodeGroup>
```typescript TypeScript
await page.act({
	action: "Type %email% into the email field",
	variables: {
		email: "john.doe@example.com",
	},
});
```
```python Python
await page.act({
	"Type %email% into the email field",
	variables={
		"email": "john.doe@example.com",
	}
})
```
</CodeGroup>

### Preview actions before running them
You can use `observe()` to get an action to run without running it.
If you're satisfied with the action, you can run it with `act()` without any LLM inference.

<CodeGroup>
```typescript TypeScript
const [topAction] = await page.observe("Click the quickstart link");

/** The action will map 1:1 with a Playwright action:
{
	description: "The quickstart link",
	method: "click",
	selector: "/html/body/div[1]/div[1]/a",
	arguments: [],
}
**/

// NO LLM INFERENCE on observe results
await page.act(topAction)
```
```python Python
actions = await page.observe("Click the quickstart link")
top_action = actions[0]

# The action will map 1:1 with a Playwright action:
# {
#		"description": "The quickstart link",
#		"method": "click",
#		"selector": "/html/body/div[1]/div[1]/a",
#		"arguments": [],
# }

# NO LLM INFERENCE on observe results
await page.act(top_action)
```
</CodeGroup>

You can also use `observe()` with sensitive information, like below.

<CodeGroup>
```typescript TypeScript
const [topAction] = await page.observe("Type %email% into the email field");

/** The observe result will be an object with the following shape:
{
	description: "The email input field",
	method: "type",
	selector: "/html/body/div[1]/div[1]/input",
	arguments: ["%email%"],
}
*/

await page.act({
	...topAction,
	// This prevents LLMs from seeing sensitive information
	// No LLM inference is taken on observe results
	arguments: [sensitiveEmail],
})
```

```python Python
actions = await page.observe("Type %email% into the email field")
top_action = actions[0]

# The observe result will be a dictionary with the following shape:
# {
#		"description": "The email input field",
#		"method": "type",
#		"selector": "/html/body/div[1]/div[1]/input",
#		"arguments": ["%email%"],
# }

await page.act({
	**top_action,
	# This prevents LLMs from seeing sensitive information
	# No LLM inference is taken on observe results
	"arguments": [sensitive_email],
})
```
</CodeGroup>

**Use `observe()` to get actionable suggestions from the current page**

<CodeGroup>
```typescript TypeScript
const actions = await page.observe();
console.log("Possible actions:", actions);

// You can also use `observe()` with a custom prompt
const buttons = await page.observe({
	instruction: "find all the buttons on the page",
});
```

```python Python
actions = await page.observe()
print("Possible actions:", actions)

# You can also use `observe()` with a custom prompt
buttons = await page.observe("find all the buttons on the page")
```
</CodeGroup>

### Avoid broad or ambiguous instructions
Avoid instructions that aren't specific to the current page or try to do multiple things at once.

<CodeGroup>
```typescript TypeScript
// Too vague
await page.act({ action: "find something interesting on the page" });

// Avoid combining actions
await page.act({ action: "fill out the form and submit it" });
```

```python Python
# Too vague
await page.act("find something interesting on the page")

# Avoid combining actions
await page.act("fill out the form and submit it")
```
</CodeGroup>

### Use `observe()` to get entire form values

<CodeGroup>
```typescript TypeScript
const formValues = await page.observe("get the text inputs on the page");
```
```python Python
form_values = await page.observe("get the text inputs on the page")
```
</CodeGroup>

This will return an array of objects with the following shape:

<CodeGroup>
```typescript TypeScript
{
	description: "The text input field",
	method: "type",
	selector: "/html/body/div[1]/div[1]/input",
	arguments: ["some text"],
}
```

```python Python
{
	"description": "The text input field",
	"method": "type",
	"selector": "/html/body/div[1]/div[1]/input",
	"arguments": ["some text"],
}
```
</CodeGroup>

You can then use these actions directly in your code.

<CodeGroup>
```typescript TypeScript
for (const formValue of formValues) {
	await page.act({
		...formValue,
		arguments: [yourValueHere],
	});
}
```

```python Python
for form_value in form_values:
	await page.act({
		**form_value,
		"arguments": [your_value_here],
	})
```
</CodeGroup>

For a full example, check out the example in the Stagehand repo [here](https://github.com/browserbase/stagehand/blob/main/examples/form_filling_sensible.ts).